//
//  Controller.m
//  snomPhone
//
//  Created by Daniel Hazelbaker on 10/22/08.
//  Copyright 2008 Blue Box Moon. All rights reserved.
//

#import "Controller.h"
#import "BMSnomPhone.h"
#import "BMPhoneStatus.h"
#import "BMPreferences.h"
#import "preferences.h"
#import "BMSnomHttpConnection.h"
#import "overlayWindow.h"
#import "overlayView.h"
#import "BMPluginController.h"
#import "SSCall.h"
#import "EMKeychainItem.h"


@implementation Controller


//
// The controller object has finished loading into memory, prepare any information we need.
//
- (void)awakeFromNib
{
    currentCalls = [[NSMutableDictionary alloc] init];
    [NSTimer scheduledTimerWithTimeInterval:0.25f target:self selector:@selector(activeCallTimer:) userInfo:nil repeats:YES];
    
    pluginController = [[BMPluginController alloc] initFromBundle:[NSBundle mainBundle]];

    //
    // Create the phone status menu item.
    //
    phoneStatus = [[BMPhoneStatus alloc] initOffline:YES];
    [phoneStatus startWorking];

    //
    // Create the basic menu for it.
    //
    [phoneStatus setMenu:[[[NSMenu alloc] initWithTitle:@"Root"] autorelease]];
    [[phoneStatus menu] setDelegate:self];
    [self setValue:[NSCalendarDate date] forKey:@"lastMenuAccess"];

    //
    // Start up the monitoring web server.
    //
    phoneHttpServer = [[HTTPServer alloc] init];
    [phoneHttpServer setConnectionClass:[BMSnomHttpConnection class]];
    [phoneHttpServer setPort:0];
    [phoneHttpServer setDelegate:self];
    [phoneHttpServer start:nil];

    //
    // Start looking up the information on the phone.
    //
    [NSThread detachNewThreadSelector:@selector(threadFindPhoneIPAddress:) toTarget:self withObject:self];

    //
    // Begin a timer to track missed calls and other information.
    //
    phoneTimer = [[NSTimer scheduledTimerWithTimeInterval:15 target:self selector:@selector(phoneTimer:) userInfo:nil repeats:YES] retain];
    [phoneTimer setFireDate:[NSDate distantFuture]];
}


//
// This timer runs periodically so that we can update information about the phone.
// One of the things we do is update the status menu to reflect missed calls.
//
- (void)phoneTimer:(NSTimer *)timer
{
    NSDictionary    *callHistory, *call, *settings;
    NSArray	    *calls;
    int		    i, missedCount = 0;
    id              outgoingIdentity = nil;


    //
    // Don't proceed if the menu is open.
    //
    if (snomPhone == nil)
	return;

    //
    // If this is our first run, setup the server connection.
    //
    if (phoneConnectionServer == nil) {
	NS_DURING
	    phoneConnectionServer = [[NSConnection alloc] init];
	    if ([phoneConnectionServer registerName:@"com.blueboxmoon.MacSnome"])
		[phoneConnectionServer setRootObject:snomPhone];
	    else
		NSLog(@"Could not register");
	NS_HANDLER
	    NSLog(@"Exception: %@", localException);
	NS_ENDHANDLER
    }

    //
    // Retrieve the call history and deal with errors.
    //
    NS_DURING
	[snomPhone loadCallHistory];
	[snomPhone loadPhoneSettings];
    settings = [snomPhone phoneSettings];
	callHistory = [snomPhone callHistory];
    NS_HANDLER
	callHistory = nil;
    NS_ENDHANDLER
    if (callHistory == nil) {
	[phoneStatus setOffline:YES];
	return;
    }
    else if ([phoneStatus isOffline] == YES)
	[phoneStatus setOffline:NO];

    //
    // See if we need to update the missed call counts.
    //
    if (lastCallHistory == nil || [[lastCallHistory valueForKey:@"missedCalls"] isEqual:[callHistory valueForKey:@"missedCalls"]] == NO) {
	calls = [callHistory valueForKey:@"missedCalls"];
	for (i = 0; i < [calls count]; i++) {
	    call = [calls objectAtIndex:i];

	    //
	    // Include this missed call if it was on or after our last menu access.
	    //
	    if ([[call valueForKey:@"callDate"] isGreaterThanOrEqualTo:lastMenuAccess] == YES)
		missedCount += 1;
	}

	[phoneStatus setMissedCallCount:missedCount];
    }

    //
    // Save the call history for next time.
    //
    [lastCallHistory autorelease];
    lastCallHistory = [callHistory retain];

    //
    // Process any phone settings that we should reflect, start with DND.
    //
    outgoingIdentity = [settings valueForKey:@"outgoing_identity"];
    if ([settings valueForKey:@"dnd_mode"] != nil)
        [phoneStatus setPhoneStatus:[[settings valueForKey:@"dnd_mode"] isEqual:@"on"] forIcon:PhoneStatus_DND];
    else if (outgoingIdentity != nil)
        [phoneStatus setPhoneStatus:[[settings valueForKey:[NSString stringWithFormat:@"dnd_mode%@", outgoingIdentity]] isEqual:@"on"] forIcon:PhoneStatus_DND];
}


//
// Set the last menu access date.  Drop the seconds.
//
- (void)setLastMenuAccess:(NSCalendarDate *)date
{
    [lastMenuAccess autorelease];
    lastMenuAccess = [[date dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-[date secondOfMinute]] retain];
}


//
// A fatal error occurred, display the message and then quit.
//
- (void)fatalError:(NSString *)message
{
    NSRunAlertPanel(@"Error", @"%@", @"Quit", nil, nil, message);
    [NSApp terminate:self];
}



#pragma mark IP Address retrieval
/////////////////////////////////////////////////////////////////////////////////////////////
//
// IP Address retrieval
//
/////////////////////////////////////////////////////////////////////////////////////////////


//
// Sub-thread to find the IP address in the background.
//
- (void)threadFindPhoneIPAddress:(id)sender
{
    NSAutoreleasePool	*mainPool, *pool;
    BMPreferences       *preferences;
    NSString            *errorString = nil, *baseURL;
    BOOL                unauthorized = NO;


    mainPool = [[NSAutoreleasePool alloc] init];
    
    //
    // Get the preferences and check if we have an existing username/password for the phone.
    //
    preferences = [BMSnomPhone preferences];
    [self setValue:[preferences valueForKey:@"phoneUsername"] forKey:@"phoneUsername"];
    if ([phoneUsername length] > 0) {
        EMGenericKeychainItem *item = [EMGenericKeychainItem genericKeychainItemForService:@"MacSnome" withUsername:phoneUsername];
        
        if (item != nil)
            [self setValue:item.password forKey:@"phonePassword"];
    }
    
    while (phoneAddress == nil || phoneAddress.length == 0) {
        //
        // Create the autorelease pool for this thread.
        //
        pool = [[NSAutoreleasePool alloc] init];
        
        //
        // Try to lookup the IP address of the phone.
        //
        NS_DURING
        phoneAddress = [[self findPhoneIPAddress] copy];
        NS_HANDLER
        errorString = [localException description];
        NS_ENDHANDLER

        if (phoneAddress == nil || phoneAddress.length == 0)
            [NSThread sleepForTimeInterval:10];

        [pool drain];
        pool = [[NSAutoreleasePool alloc] init];
    }

    while (snomPhone == nil) {
        //
        // Create the autorelease pool for this thread.
        //
        pool = [[NSAutoreleasePool alloc] init];

        //
        // Create the phone object.
        //
        NS_DURING
        unauthorized = NO;
        snomPhone = [[BMSnomPhone alloc] initWithAddress:phoneAddress username:phoneUsername password:phonePassword];
        NS_HANDLER
        if ([[localException reason] isEqual:@"401 Unauthorized"])
            unauthorized = YES;
        else if ([localException.reason isEqualToString:@"Cannot contact phone web service."] == NO)
            NSLog(@"Exception = %@", localException);
        
        snomPhone = nil;
        NS_ENDHANDLER
        
        //
        // Check for authorization error.
        //
        if (unauthorized == YES) {
            NSBeep();
            [self setValue:@"" forKey:@"phonePassword"];
            [NSApp runModalForWindow:passwordWindow];
        }
        else if ([phoneUsername length] > 0) {
            EMGenericKeychainItem *item = [EMGenericKeychainItem genericKeychainItemForService:@"MacSnome" withUsername:phoneUsername];
            
            if (item != nil)
                item.password = phonePassword;
            else
                [EMGenericKeychainItem addGenericKeychainItemForService:@"MacSnome" withUsername:phoneUsername password:phonePassword];

            [preferences setValue:phoneUsername forKey:@"phoneUsername"];
        }
        
        [NSThread sleepForTimeInterval:10];

        [pool drain];
        pool = [[NSAutoreleasePool alloc] init];
    }

    //
    // Update the phone action URL settings.
    //
    baseURL = [NSString stringWithFormat:@"http://%@:%d/", [self primaryLocalAddress], [phoneHttpServer port]];
    [snomPhone setPhoneSetting:[self urlencode:[NSString stringWithFormat:@"%@incoming?remote=$remote&csta_id=$csta_id&callerid=$display_remote", baseURL]] forKey:@"action_incoming_url"];
    [snomPhone setPhoneSetting:[self urlencode:[NSString stringWithFormat:@"%@outgoing?remote=$remote&csta_id=$csta_id", baseURL]] forKey:@"action_outgoing_url"];
    [snomPhone setPhoneSetting:[self urlencode:[NSString stringWithFormat:@"%@connected?csta_id=$csta_id", baseURL]] forKey:@"action_connected_url"];
    [snomPhone setPhoneSetting:[self urlencode:[NSString stringWithFormat:@"%@disconnected?csta_id=$csta_id", baseURL]] forKey:@"action_disconnected_url"];
    
    //
    // We are done working.
    //
    [phoneStatus stopWorking];

    //
    // Fire off the timer.
    //
    [phoneTimer setFireDate:[NSDate date]];

    [pool drain];
    [mainPool drain];
}


//
// Determine the IP address of the Snom Phone by any means neccessary.
// 1. Specific IP address given.
// 2. MAC address of phone given, run shell commands to determine the IP address.
// 2. Plugins
// 3. Cached IP address.
//
- (NSString *)findPhoneIPAddress
{
    BMPreferences	*preferences = [BMSnomPhone preferences];
    NSString		*ipAddress = nil;


    //
    // Check for static IP address.
    //
    if ([[preferences valueForKey:PREFERENCE_IPADDRESS] length] != 0)
	return [preferences valueForKey:PREFERENCE_IPADDRESS];

    //
    // Use all the plugins.
    //
    if ((ipAddress = [pluginController lookupPhoneAddress]) != nil)
	return ipAddress;

    return nil;
}


//
// Determine the primary local address.
//
- (NSString *)primaryLocalAddress
{
    NSString *address;
    NSArray *addresses = [[NSHost currentHost] addresses];
    int i;
    
    
    for (i = 0; i < [addresses count]; i++) {
	address = [addresses objectAtIndex:i];

	if ([address rangeOfString:@":"].location == NSNotFound)
	    return address;
    }
    
    return @"127.0.0.1";
}


//simple API that encodes reserved characters according to:
//RFC 3986
//http://tools.ietf.org/html/rfc3986
- (NSString *)urlencode:(NSString *)url
{
    NSArray *escapeChars = [NSArray arrayWithObjects:@";" , @"/" , @"?" , @":" ,
			    @"@" , @"&" , @"=" , @"+" ,
			    @"$" , @"," , @"[" , @"]",
			    @"#", @"!", @"'", @"(", 
			    @")", @"*", nil];
    
    NSArray *replaceChars = [NSArray arrayWithObjects:@"%3B" , @"%2F" , @"%3F" ,
			     @"%3A" , @"%40" , @"%26" ,
			     @"%3D" , @"%2B" , @"%24" ,
			     @"%2C" , @"%5B" , @"%5D", 
			     @"%23", @"%21", @"%27",
			     @"%28", @"%29", @"%2A", nil];
    
    int len = [escapeChars count];
    
    NSMutableString *temp = [url mutableCopy];
    
    int i;
    for(i = 0; i < len; i++)
    {
	
        [temp replaceOccurrencesOfString: [escapeChars objectAtIndex:i]
			      withString:[replaceChars objectAtIndex:i]
				 options:NSLiteralSearch
				   range:NSMakeRange(0, [temp length])];
    }
    
    return [NSString stringWithString: temp];
}


#pragma mark User actions.
/////////////////////////////////////////////////////////////////////////////////////////////
//
// User actions.
//
/////////////////////////////////////////////////////////////////////////////////////////////


//
// User wants to save the password and attempt again.
//
- (IBAction)savePassword:(id)sender
{
    [passwordWindow orderOut:self];
    [NSApp stopModalWithCode:0];
}



#pragma mark NSMenu delegate methods.
/////////////////////////////////////////////////////////////////////////////////////////////
//
// NSMenu delegate methods.
//
/////////////////////////////////////////////////////////////////////////////////////////////


//
// The menu in the NSStatusItem is about to be displayed, show full data.
//
- (void)menuNeedsUpdate:(NSMenu *)menu
{
    if ([[menu title] isEqual:@"Root"])
	[self rootMenuNeedsUpdate:menu];
    else if ([[menu title] isEqual:@"History"])
	[self historyMenuNeedsUpdate:menu];
}


//
// The root menu needs to be updated, set the information it should display.
//
- (void)rootMenuNeedsUpdate:(NSMenu *)menu
{
    NSDictionary    *phoneSettings;
    NSMenuItem	    *item;
    NSImage	    *image;
    NSMenu	    *subMenu;
    id          outgoingIdentity = nil;


    //
    // Update phone status.
    //
    NS_DURING
	[snomPhone loadCallHistory];
	[snomPhone loadPhoneSettings];
	phoneSettings = [snomPhone phoneSettings];
    NS_HANDLER
	phoneSettings = nil;
    NS_ENDHANDLER
    if (phoneSettings == nil)
	[phoneStatus setOffline:YES];
    else if ([phoneStatus isOffline] == YES)
	[phoneStatus setOffline:NO];

    //
    // Clear out the old menu.
    //
    while ([menu numberOfItems] > 0)
	[menu removeItemAtIndex:0];

    //
    // Check for phone offline state.
    //
    if ([phoneStatus isOffline] == YES)
	[menu addItemWithTitle:@"Phone is offline" action:nil keyEquivalent:@""];
    else {
	//
	// Add the Do-Not-Disturb phone setting.
	//
	item = [menu addItemWithTitle:@"Do-Not-Disturb" action:@selector(toggleDoNotDisturb:) keyEquivalent:@""];
	[item setTarget:self];
	image = [NSImage imageNamed:@"dnd"];
	[image setScalesWhenResized:YES];
	[image setSize:NSMakeSize(12.0f, 12.0f)];
	[item setImage:image];
        outgoingIdentity = [phoneSettings valueForKey:@"outgoing_identity"];
        if ([phoneSettings valueForKey:@"dnd_mode"] != nil)
            [item setState:[[phoneSettings valueForKey:@"dnd_mode"] isEqual:@"on"]];
        else if (outgoingIdentity != nil)
            [item setState:[[phoneSettings valueForKey:[NSString stringWithFormat:@"dnd_mode%@", outgoingIdentity]] isEqual:@"on"]];

	//
	// Seperator.
	//
	[menu addItem:[NSMenuItem separatorItem]];

	//
	// Add the call history sub-menu.
	//
	item = [menu addItemWithTitle:@"Call History" action:nil keyEquivalent:@""];
	subMenu = [[[NSMenu alloc] initWithTitle:@"History"] autorelease];
	[subMenu setDelegate:self];
	[item setSubmenu:subMenu];
    }

    //
    // Seperator.
    //
    [menu addItem:[NSMenuItem separatorItem]];

    //
    // Add the configure item.
    //
    item = [menu addItemWithTitle:@"Configure" action:@selector(configure:) keyEquivalent:@""];
    [item setTarget:self];

    //
    // Add the quit item.
    //
    item = [menu addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@""];
    [item setTarget:NSApp];
}


//
// Build the call history menu.
//
- (void)historyMenuNeedsUpdate:(NSMenu *)menu
{
    NSDictionary    *callHistory, *call;
    NSMenuItem	    *item;
    NSArray	    *calls;
    int		    i;


    //
    // Store the time of this menu access.
    //
    [self setValue:[NSCalendarDate date] forKey:@"lastMenuAccess"];

    //
    // Clear out the old menu.
    //
    while ([menu numberOfItems] > 0)
	[menu removeItemAtIndex:0];

    //
    // Get the call history.
    //
    callHistory = [snomPhone callHistory];

    //
    // Process missed calls.
    //
    calls = [callHistory valueForKey:@"missedCalls"];
    [menu addItemWithTitle:@"Missed" action:nil keyEquivalent:@""];
    for (i = 0; i < [calls count]; i++) {
	call = [calls objectAtIndex:i];
	item = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ (%@)", [call valueForKey:@"name"], [call valueForKey:@"relativeDateTime"]]
			action:@selector(dialHistoryCall:) keyEquivalent:@""];
	[item setTarget:self];
	[item setRepresentedObject:call];
    }

    //
    // Process dialed calls.
    //
    calls = [callHistory valueForKey:@"dialedCalls"];
    [menu addItemWithTitle:@" " action:nil keyEquivalent:@""];
    [menu addItemWithTitle:@"Dialed" action:nil keyEquivalent:@""];
    for (i = 0; i < [calls count]; i++) {
	call = [calls objectAtIndex:i];
	item = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ (%@, %@)", [call valueForKey:@"name"], [call valueForKey:@"relativeDateTime"], [call valueForKey:@"relativeShortDuration"]]
			action:@selector(dialHistoryCall:) keyEquivalent:@""];
	[item setTarget:self];
	[item setRepresentedObject:call];
    }

    //
    // Process received calls.
    //
    calls = [callHistory valueForKey:@"receivedCalls"];
    [menu addItemWithTitle:@" " action:nil keyEquivalent:@""];
    [menu addItemWithTitle:@"Received" action:nil keyEquivalent:@""];
    for (i = 0; i < [calls count]; i++) {
	call = [calls objectAtIndex:i];
	item = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ (%@, %@)", [call valueForKey:@"name"], [call valueForKey:@"relativeDateTime"], [call valueForKey:@"relativeShortDuration"]]
			action:@selector(dialHistoryCall:) keyEquivalent:@""];
	[item setTarget:self];
	[item setRepresentedObject:call];
    }
}


//
// A menu is going to close, if it is the call history then reset the missed
// call count.
//
- (void)menuDidClose:(NSMenu *)menu
{
    if ([[menu title] isEqual:@"History"])
	[phoneStatus setMissedCallCount:0];
}



#pragma mark Menu commands.
/////////////////////////////////////////////////////////////////////////////////////
//
// Menu commands.
//
/////////////////////////////////////////////////////////////////////////////////////


//
// Toggle the state of the DND button.
//
- (void)toggleDoNotDisturb:(NSMenuItem *)sender
{
    NSDictionary    *phoneSettings;
    NSString        *outgoingIdentity, *dndSetting;
    
    
    NS_DURING
	phoneSettings = [snomPhone phoneSettings];
    NS_HANDLER
	phoneSettings = nil;
    NS_ENDHANDLER
    if (phoneSettings == nil)
        return;

    outgoingIdentity = [phoneSettings valueForKey:@"outgoing_identity"];
    if ([phoneSettings valueForKey:@"dnd_mode"] != nil)
        dndSetting = @"dnd_mode";
    else if (outgoingIdentity != nil)
        dndSetting = [NSString stringWithFormat:@"dnd_mode%@", outgoingIdentity];
    else
        return;

    if ([sender state])
        [snomPhone setPhoneSetting:@"off" forKey:dndSetting];
    else
        [snomPhone setPhoneSetting:@"on" forKey:dndSetting];

    [phoneTimer fire];
}


//
// Dial the history call back.
//
- (void)dialHistoryCall:(NSMenuItem *)sender
{
    [snomPhone dialPhoneNumber:[[sender representedObject] valueForKey:@"number"]];
}


//
// User wants to configure the phone settings.
//
- (void)configure:(id)sender
{
    NSString	*path;


    path = [@"~/Library/PreferencePanes/MacSnome.prefPane" stringByExpandingTildeInPath];
    if ([[NSFileManager defaultManager] fileExistsAtPath:path] == YES)
	[[NSWorkspace sharedWorkspace] openFile:path];
    else {
	path = @"/Library/PreferencePanes/MacSnome.prefPane";
	if ([[NSFileManager defaultManager] fileExistsAtPath:path] == YES)
	    [[NSWorkspace sharedWorkspace] openFile:path];
    }
}



#pragma mark Snom Http Connection delegate methods.
///////////////////////////////////////////////////////////////////////////////////////
//
// Snom Http Connection delegate methods.
//
///////////////////////////////////////////////////////////////////////////////////////


- (void)processIncomingCall:(NSDictionary *)parameters
{
    NSDictionary    *person;
    NSString	    *remoteNumber, *callID;
    NSMutableArray  *possiblePhoneNumbers;
    SSCall          *call;


    //
    // Get the remote phone number.
    //
    remoteNumber = [[[parameters objectForKey:@"remote"] componentsSeparatedByString:@"@"] objectAtIndex:0];
    if ([remoteNumber characterAtIndex:0] == '9' && [remoteNumber length] > 7 && true /* TODO: Country code == US or something */)
	remoteNumber = [remoteNumber substringFromIndex:1];
    callID = [parameters objectForKey:@"csta_id"];

    //
    // Build a search list of phone numbers and query the address book.
    //
    possiblePhoneNumbers = [NSMutableArray array];
    if ([remoteNumber characterAtIndex:0] == '1' && [remoteNumber length] >= 11)
        [possiblePhoneNumbers addObject:[remoteNumber substringFromIndex:1]];
    else
        [possiblePhoneNumbers addObject:remoteNumber];
    person = [pluginController lookupPersonForPhoneNumbers:possiblePhoneNumbers];

    //
    // If no person was found then create a pseudo person from the caller ID
    // information.
    //
    if (person == nil)
    {
	NSString    *callerID, *firstName, *lastName;
	NSInteger   idx;
	
	
	callerID = [parameters objectForKey:@"callerid"];
	idx = [callerID rangeOfString:@" "].location;
	if (idx != NSNotFound)
	{
	    firstName = [callerID substringToIndex:idx];
	    lastName = [callerID substringFromIndex:(idx + 1)];
	    person = [NSDictionary dictionaryWithObjectsAndKeys:
		      firstName, kPLFirstNameKey,
		      lastName, kPLLastNameKey,
              [BMSnomPhone formatPhoneNumber:remoteNumber], kPLPhoneNumberKey,
		      nil];
	}
    }

    //
    // Save the call.
    //
    call = [[[SSCall alloc] init] autorelease];
    call.callID = callID;
    call.person = person;
    call.remoteNumber = [BMSnomPhone formatPhoneNumber:remoteNumber];
    call.state = CallState_Pending;
    call.direction = CallDirection_Incoming;
    [currentCalls setObject:call forKey:call.callID];
    
    [pluginController notifyIncomingCall:call];
}


- (void)processOutgoingCall:(NSDictionary *)parameters
{
    NSDictionary    *person;
    NSString	    *remoteNumber, *callID;
    NSArray	    *possiblePhoneNumbers;
    SSCall	    *call;
    
    
    //
    // Get the remote phone number.
    //
    remoteNumber = [[[parameters objectForKey:@"remote"] componentsSeparatedByString:@"@"] objectAtIndex:0];
    if ([remoteNumber characterAtIndex:0] == '9' && [remoteNumber length] > 7 && true /* TODO: Country code == US or something */)
	remoteNumber = [remoteNumber substringFromIndex:1];
    callID = [parameters objectForKey:@"csta_id"];
    
    //
    // Build a search list of phone numbers and query the address book.
    //
    possiblePhoneNumbers = [NSArray arrayWithObjects:
			    remoteNumber,
			    [NSString stringWithFormat:@"7602452415%@", remoteNumber],
			    [NSString stringWithFormat:@"17602452415%@", remoteNumber],
			    nil];
    person = [pluginController lookupPersonForPhoneNumbers:possiblePhoneNumbers];
    
    //
    // Save the call.
    //
    call = [[[SSCall alloc] init] autorelease];
    call.callID = callID;
    call.person = person;
    call.remoteNumber = [BMSnomPhone formatPhoneNumber:remoteNumber];
    call.state = CallState_Pending;
    call.direction = CallDirection_Outgoing;
    [currentCalls setObject:call forKey:call.callID];

    //
    // Notify plugins.
    //
    [pluginController notifyOutgoingCall:call];
}


//
// A call has connected, mark it.
//
- (void)processConnectedCall:(NSDictionary *)parameters
{
    SSCall *call;
    
    
    call = [currentCalls objectForKey:[parameters objectForKey:@"csta_id"]];
    if (call != nil)
    {
	call.state = CallState_Active;
	call.activeDate = [NSDate date];
    }
}


//
// A call has disconnected, remove it.
//
- (void)processDisconnectedCall:(NSDictionary *)parameters
{
    SSCall *call;
    
    
    call = [currentCalls objectForKey:[parameters objectForKey:@"csta_id"]];
    if (call != nil)
    {
	[pluginController notifyCallEnded:call];
	[call retain];
	[currentCalls removeObjectForKey:call.callID];
	[call.popups makeObjectsPerformSelector:@selector(dismiss)];
	[call autorelease];
    }
}


//
// Track the active call and update the text displayed.
//
- (void)activeCallTimer:(NSTimer *)timer
{
    NSMutableString *string;
    NSArray	    *calls;
    SSCall	    *call = nil;
    int		    i, seconds;
    
    
    calls = [currentCalls allValues];
    for (i = 0; i < [calls count]; i++)
    {
	if (((SSCall *)[calls objectAtIndex:i]).state == CallState_Active)
	{
	    call = [calls objectAtIndex:i];
	    break;
	}
    }
    if (call == nil)
    {
	if (phoneStatus.phoneText != nil)
	    phoneStatus.phoneText = nil;

	return;
    }
    
    //
    // Update the call status name.
    //
    string = [NSMutableString string];
    if (call.person != nil)
	[string appendFormat:@"%@ %@", [call.person valueForKey:kPLFirstNameKey], [call.person valueForKey:kPLLastNameKey]];
    else
	[string appendString:[[snomPhone class] formatPhoneNumber:call.remoteNumber]];

    //
    // Update the call status duration.
    //
    seconds = -[call.activeDate timeIntervalSinceNow];
    [string appendFormat:@" %d:%02d", (seconds / 60), (seconds % 60)];

    if (phoneStatus.phoneText == nil || [string isEqualToString:phoneStatus.phoneText] == NO)
	phoneStatus.phoneText = string;
}


@end
